#pragma once
#include "fiber_cpp_define.hpp"
#include <thread>
#include <functional>
#include "fiber.hpp"
#include "fiber_tbox.hpp"

// __cplusplus:
//    199711L (C++98 or C++03)
//    201103L (C++11)
//    201402L (C++14)
//    201703L (C++17)
//    202002L (C++20)

#if __cplusplus >= 201103L      // Support c++11 ?

struct ACL_FIBER;

namespace acl {

class fiber_ctx {
public:
	fiber_ctx(std::function<void()> fn) {
		fn_ = std::move(fn);
	}

	~fiber_ctx() = default;

	std::function<void()> fn_;
};

#define	go                  acl::go_fiber()>
#define	go_stack(size)      acl::go_fiber(size, false)>
#define	go_share(size)      acl::go_fiber(size, true)>

#define	go_wait_fiber       acl::go_fiber()<
#define	go_wait_thread      acl::go_fiber()<<
#define	go_wait             go_wait_thread

class go_fiber {
public:
	go_fiber(void) {}
	go_fiber(size_t stack_size, bool on) : stack_size_(stack_size), stack_share_(on) {}

	ACL_FIBER* operator > (std::function<void()> fn) {
		fiber_ctx* ctx = new fiber_ctx(fn);
		return fiber::fiber_create(fiber_main, (void*) ctx, stack_size_, stack_share_);
	}

	void operator < (std::function<void()> fn) {
		fiber_tbox<int> box;

		go[&] {
			fn();
			box.push(NULL);
		};
		(void) box.pop();
	}

	void operator << (std::function<void()> fn) {
		fiber_tbox<int> box;

		std::thread thread([&]() {
			fn();
			box.push(NULL);
		});

		thread.detach();
		(void) box.pop();
	}

private:
	size_t stack_size_  = 320000;
	bool   stack_share_ = false;

	static void fiber_main(ACL_FIBER*, void* ctx) {
		fiber_ctx* fc = (fiber_ctx *) ctx;
		std::function<void()> fn = fc->fn_;
		delete fc;

		fn();
	}
};

} // namespace acl

#endif // __cplusplus >= 201103L

/**
 * static void fiber1(void) {
 * 	printf("fiber: %d\r\n", acl::fiber::self());
 * }
 *
 * static void fiber2(acl::string& buf) {
 * 	printf("in fiber: %d, buf: %s\r\n", acl::fiber::self(), buf.c_str());
 * 	buf = "world";
 * }
 *
 * static void fiber3(const acl::string& buf) {
 * 	printf("in fiber: %d, buf: %s\r\n", acl::fiber::self(), buf.c_str());
 * }
 *
 * static void incr(int& n) {
 *	n++;
 * }
 *
 * static void waiter(void) {
 *	int n = 100;
 *
 *	// run in thread and wait for result
 *	go_wait_thread[&] { incr(n); };
 *  // here: n should be 101
 *
 *	n = 200;
 *
 *	// run in fiber and wait for result
 *	go_wait_fiber[&] { incr(n); };
 *  // here: n should be 201
 * }
 *
 * static test(void) {
 * 	go fiber1;
 * 	
 * 	acl::string buf("hello");
 *
 * 	go[&] {
 * 		fiber2(buf);
 * 	};
 * 	
 * 	go[=] {
 * 		fiber3(buf);
 * 	};
 * 
 * 	go[&] {
 * 		fiber3(buf);
 * 	};
 * }
 */
